//
//  TransfersController.m
//  SolarSeek
//
//  Created by Erick Fejta on 05/02/01.
//  Copyright 2005 Erick Fejta. All rights reserved.
//

#import "TransfersController.h"

#import "Transfers.h"
#import "Shares.h"
#import "ShareController.h"
#import "TransfersQueue.h"
#import "PeerConnectionsController.h"
#import "Connections.h"
#import "Message.h"
#import "Peers.h"
#import "ConnectionsIdentifier.h"
#import "SolarSeekEngine.h"
#import "ServerConnectionController.h"
#import "PeersController.h"
#import "NetSocket.h"

@interface TransfersController (Private)
- (void)addTransfer:(Transfer *)transfer;
- (void)removeTransfer:(Transfer *)transfer;
@end 

@interface TransfersController (KeyValueObserving)
- (Transfer *)transferWithToken:(unsigned)token;
- (Transfer *)transferWithFilename:(NSString *)filename peer:(Peer *)peer type:(TransferType)type;
- (void)observeTransferConnection:(TransferConnection *)transferConnection;
- (void)requestPeerToConnectForTransfer:(Transfer *)transfer;
- (void)sendPlaceInQueueForFilename:(NSString *)filename peerConnection:(PeerConnection *)peerConnection;
- (void)sendRequestToUploadTransfer:(TransferUpload *)transfer ;
- (void)stopObservingTransferConnection:(TransferConnection *)transferConnection;
- (void)connectForTransferWithPeerConnection:(PeerConnection *)peerConnection transfer:(Transfer *)transfer;
- (void)endRequestPeerToConnectForTransferWithToken:(NSNumber *)tokenNumber ;
- (void)endTransferConnection:(TransferConnection *)transferConnection ;
- (void)failTransfer:(Transfer *)transfer reason:(NSString *)reason retryAfterDelay:(NSTimeInterval)delay;
- (void)failTransferWithToken:(unsigned)token peer:(Peer *)peer reason:(NSString *)reason;
- (void)requestTransfer:(Transfer *)transfer;
- (void)processFolderContentsResponse:(Message *)message fromPeerConnection:(PeerConnection *)peerConnection;
- (void)processQueueResponseWithCode:(int)code message:(Message *)message peerConnection:(PeerConnection *)peerConnection;
- (void)processTransferRequest:(Message *)message fromPeerConnection:(PeerConnection *)peerConnection;
- (void)processTransferResponse:(Message *)message fromPeerConnection:(PeerConnection *)peerConnection;
- (void)processMessageFromServer;
- (void)queueUploadWithFilename:(NSString *)filename peerConnection:(PeerConnection *)peerConnection;
- (void)waitForConnectionWithToken:(unsigned)token ForTransfer:(Transfer *)t size:(unsigned)size;
@end

typedef enum { ObserveAvailable=1 } TransfersControllerObservering;

@implementation TransfersController

- (id)init {
	[NSException raise:@"InvalidArgumentException" format:@"[ServerConnectionController init] is not a valid initializer."];
	[self release];
	return nil;
}


- (id)initWithEngine:(SolarSeekEngine *)engine {
	if(self = [super init]) {
		[NSObject setKeys:[NSArray arrayWithObjects:@"transferCreated",@"transferRemoved",nil] 
				triggerChangeNotificationsForDependentKey:@"transfersAll"];
		[NSObject setKeys:[NSArray arrayWithObjects:@"transferCreated",@"transferRemoved",nil] 
				triggerChangeNotificationsForDependentKey:@"transfersDownload"];
		[NSObject setKeys:[NSArray arrayWithObjects:@"transferCreated",@"transferRemoved",nil] 
				triggerChangeNotificationsForDependentKey:@"transfersUpload"];
		_engine = engine;
		_connectionsIdentifier = [[engine connectionsIdentifier] retain];
			[_connectionsIdentifier addObserver:self forKeyPath:@"connectionOpened" options:nil context:nil];
		_serverConnection = [[engine serverConnection] retain];
			[_serverConnection addObserver:self forKeyPath:@"messageReceived" options:nil context:nil];
		_shareController = [[engine shareController] retain];
		_peerConnectionsController = [[engine peerConnectionsController] retain];
			[_peerConnectionsController addObserver:self forKeyPath:@"peerConnectionWithNewMessage" options:nil context:nil];
			[_peerConnectionsController addObserver:self forKeyPath:@"peerConnectionUnableToConnect" options:nil context:nil];
		_transfersQueue = [[TransfersQueue alloc] init];
			[_transfersQueue addObserver:self forKeyPath:@"transferExiting" options:nil context:nil];

		_tokensTransferIncomingRequested = [[NSMutableDictionary alloc] initWithCapacity:20];
		_tokensFoldersDownloading = [[NSMutableDictionary alloc] initWithCapacity:5];
		
		_transfersManuallyStopped = [[NSMutableSet alloc] initWithCapacity:5];
		_transfersWaitingForConnection = [[NSMutableSet alloc] initWithCapacity:20];
		_transferConnectionsSet = [[NSMutableSet alloc] init];
		_transfersSet = [[NSMutableSet alloc] init];
		
		_transferCreated = _transferFailed = _transferFinished = _transferRemoved = _transferStopped = _transferStarted = nil;
	}
	return self;
}

- (void)dealloc {
		[_connectionsIdentifier removeObserver:self forKeyPath:@"connectionOpened"];
	[_connectionsIdentifier release];
	
		[_serverConnection removeObserver:self forKeyPath:@"messagedReceived"];
	[_serverConnection release];

	[_shareController release];
	
	[_peerConnectionsController removeObserver:self forKeyPath:@"peerConnectionWithNewMessage"];
	[_peerConnectionsController release];
	
	[_tokensTransferIncomingRequested release];
	[_tokensFoldersDownloading release];
	
	[_transfersManuallyStopped release];
	[_transfersWaitingForConnection release];
	[_transferConnectionsSet release];
	[_transfersSet release];
	
		[_transfersQueue removeObserver:self forKeyPath:@"transferExiting"];
	[_transfersQueue release];
	
	[_transferCreated release];
	[_transferFailed release];
	[_transferFinished release];
	[_transferRemoved release];
	[_transferStopped release];
	[_transferStarted release];
	
	[super dealloc];
}


- (void)attachConnection:(UnidentifiedConnection *)uc {
	if([[uc type] isEqual:TypeConnectionTransfer]) {
		Peer *p = [[_peerConnectionsController peerConnectionForName:[uc name]] peer];
		TransferConnection *tc = [[TransferConnection alloc] initWithUnidentifiedConnection:uc peer:p];
		[self observeTransferConnection:tc];
		[tc addObserver:self forKeyPath:@"token" options:nil context:nil];
		[tc identifyTransfer];
		[tc release];
	}
	else if([[uc type] isEqual:TypeConnectionPierceFirewall]) {
		NSNumber *tokenId = [NSNumber numberWithUnsignedInt:[uc token]];
		Transfer *t = [_tokensTransferIncomingRequested objectForKey:tokenId];
		if(t) {
			[t retain];
			[NSObject cancelPreviousPerformRequestsWithTarget:_tokensTransferIncomingRequested selector:@selector(endRequestPeerToConnectForTransferWithToken:) object:tokenId];
			[_tokensTransferIncomingRequested removeObjectForKey:tokenId];
			[t autorelease];
			Peer *p = [t peer];
			TransferConnection *tc = [[TransferConnection alloc] initWithUnidentifiedConnection:uc peer:p transfer:t];
			[self observeTransferConnection:tc];
			[t setState:TransferStateStarting description:@"Initializing....."];
			[tc addObserver:self forKeyPath:@"dataTransferred" options:nil context:nil];
			[tc identifyTransfer];
			[tc release];
		}
	}
}

- (unsigned)countTransfersAllowed {
	unsigned max = [_transfersQueue countCeilingTransfersExitingTotal];
	unsigned cur = [self countTransfersCurrent];
	return max-cur>10?cur+10:max;
}

- (unsigned)countTransfersCurrent { return [_transfersQueue countTransfersExitingTotal]; }
- (unsigned)countTransfersQueued { return [_transfersQueue countTransfersQueued]; }


- (void)sendUserInfoMessageToPeer:(Peer *)peer {
	NSUserDefaults *u = [NSUserDefaults standardUserDefaults];
	PeerConnection *pc = [[[NSApp engine] peerConnectionsController] peerConnectionForPeer:peer];
	Message *m = [Message messageWithCode:UserInfoReply];
	NSString *comment = [[[NSApp engine] me] comment];
	[m putString:comment];
	NSData *picture = [[[NSApp engine] me] picture];
	if(picture && [picture length] > 0) {
		[m putByte:1];
		[m putData:picture];
	}
	else
		[m putByte:0];  //no pic
	unsigned allowed = [self countTransfersAllowed];
	[m putInt:allowed]; //total slots
	[m putInt:[self countTransfersQueued]];	//queue
	[m putByte:allowed - [self countTransfersCurrent]];	//avail slots.
	[pc sendMessage:m];
}


- (void)dispatchMessage:(Message *)m fromPeerConnection:(PeerConnection *)pc {
	[m rewind];
	unsigned code = [m getCode];
	switch(code) {
		case UserInfoRequest: {
			[NSApp appendLogWithHeader:@"PEER"
				HTML:[[[pc peer] name] stringByAppendingString:@" requested our user information and picture."]
				type:LogTypePeer];
			[self sendUserInfoMessageToPeer:[pc peer]];
			break;
		}
		case FolderContentsResponse: {
			[self processFolderContentsResponse:m fromPeerConnection:pc];
			break;
		}
		case TransferResponse: {
			[self processTransferResponse:m fromPeerConnection:pc];
			break;
		}
		case TransferRequest: {
			[self processTransferRequest:m fromPeerConnection:pc];
			break;
		}
		case PlaceInQueue: {
			[self processQueueResponseWithCode:code message:m peerConnection:pc]; 
			break;
		}
		case PlaceInQueueRequest: {
			[self sendPlaceInQueueForFilename:[m getString] peerConnection:pc];
			break;
		}
		case PlaceholdUpload: {
			[self queueUploadWithFilename:[m getString] peerConnection:pc];
			break;
		}
		case QueueUpload: {
			[self queueUploadWithFilename:[m getString] peerConnection:pc];
//			[_transfersQueue scheduleUpdateStatus];
			break;
		}
		case QueueFailed: {
			[self processQueueResponseWithCode:code message:m peerConnection:pc];
		}
		case UploadFailed: {
			NSString *filename = [m getString];
			Transfer *t = [self transferWithFilename:filename peer:[pc peer] type:TransferTypeDownload];
			switch([t state]) {
				TransferStateIdle:
				TransferStateStarting:
				//	NSLog(@"Upload failed but idle or starting.");
					[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(downloadShare:) object:[t share]];
					[self downloadShare:(SharePeer *)[t share]];
					break;
				default:
					[self failTransfer:t reason:@"Failed" retryAfterDelay:120.0];
			}
		}
		//fejta: TODO: HIGH: FolderContentsRequest FolderContentsResponse
	}
}

- (void)attachTransferConnection:(TransferConnection *)tc WithToken:(unsigned)tokennew {
	Transfer *tra;
	NSEnumerator *e = [[_transfersWaitingForConnection allObjects] objectEnumerator];
	while(tra = [e nextObject]) {
		unsigned tokentrans = [tra tokenPeerChosen];
		if(tokentrans == tokennew && [[tra peer] isEqual:[tc peer]]) {
//			NSLog(@"MATCH: MATCHED %d to %d for %@.",tokennew,tokentrans,[tc peer]);
			[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(timeoutWaitingForConnectionForTransfer:) object:tra];
			[tra setState:TransferStateStarting description:@"Initializing...."];
			//because the hash will change!
			{
				[tc retain];
				[_transferConnectionsSet removeObject:tc]; 
				[tc setTransfer:tra];
				[_transferConnectionsSet addObject:tc]; //because the hash changed!
				[tc release];
			}
			[_transfersWaitingForConnection removeObject:tra];
			[tc addObserver:self forKeyPath:@"dataTransferred" options:nil context:nil];
			break;
		}
//		else NSLog(@"MATCH: Do these two tokens match: %d and %d (%d, %d)?",tokentrans,tokennew,[tra peer],[tc peer]);
	}
	if([tc transfer] == nil) {
		NSLog(@"MATCH FAIL: could not find a transfer for %@ and %d",[tc peer],tokennew);
		[tc disconnect];
	}
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
	TransfersControllerObservering tco = (TransfersControllerObservering)context;
	switch(tco) {
		case ObserveAvailable: {
			//restart failed downloads if he was offline.
			Peer *p = object;
			switch([p availability]) {
				case AvailabilityAway:
				case AvailabilityOnline: {
					Availability old = [[change objectForKey:NSKeyValueChangeOldKey] intValue];
					if(old == AvailabilityOffline) {
						NSEnumerator *e = [[self transfersForPeer:p withType:TransferTypeDownload] objectEnumerator];
						TransferDownload *d;
						while(d = [e nextObject]) {
							if([d state]!=TransferStateFailed || [d state]!=TransferStateFinished)
								[self downloadShare:(SharePeer *)[d share]];
						}
					}
					break;
				}
				case AvailabilityOffline: {
					NSEnumerator *e = [[self transfersForPeer:p] objectEnumerator];
					Transfer *t;
					while(t = [e nextObject]) {
						switch([t state]) {
							case TransferStateFailed:
							case TransferStateStarting:
							case TransferStateIdle:
								[self failTransfer:t reason:@"Logged Off" retryAfterDelay:0];
								break;
						}
					}
					break;
				}
				case AvailabilityUnknown: {
					if([p exists]==NO) {
						NSEnumerator *e = [[self transfersForPeer:p] objectEnumerator];
						Transfer *t;
						while(t = [e nextObject]) {
							switch([t state]) {
								case TransferStateFailed:
								case TransferStateStarting:
								case TransferStateIdle:
									[self failTransfer:t reason:[NSString stringWithFormat:@"%@ does not exist",[p name]] retryAfterDelay:500.0];
									break;
							}
						}
					}
					break;
				}
			}
			break;
		}
		
		default:
		if(object == _transfersQueue) {
			NSAssert1([keyPath isEqual:@"transferExiting"],@"Transfers Queue observed unexpected keyPath %@.",keyPath);
			[self sendRequestToUploadTransfer:(TransferUpload *)[_transfersQueue transferExiting]];
			
		}
		else if(object == _connectionsIdentifier) {
			NSAssert1([keyPath isEqual:@"connectionOpened"],@"Observed strange keyPath:%@ on connections identifier",keyPath);
			[self attachConnection:[_connectionsIdentifier connectionOpened]];
		}
		else if(object == _serverConnection && [keyPath isEqual:@"messageReceived"]) {
			[self processMessageFromServer];
		}
		else if([object isKindOfClass:[TransferConnection class]] ) {
			TransferConnection *tc = object;
			if([keyPath isEqual:@"connected"]) {
				//sendMessagesQueued will be called.
				//identifyTransfer will then be called.
				Transfer *t = [tc transfer];
				if([tc connected]) {
					[t setState:TransferStateStarting description:@"Initializing..."];
					[tc addObserver:self forKeyPath:@"dataTransferred" options:nil context:nil];
				}
				else {
					[tc removeObserver:self forKeyPath:@"dataTransferred"];
					[self endTransferConnection:tc];
				}
			}
			else if([keyPath isEqual:@"token"]) {
				[tc removeObserver:self forKeyPath:@"token"];
				unsigned tokennew = [tc token];
				[self attachTransferConnection:tc WithToken:tokennew];
			}
			else {
				NSAssert2([keyPath isEqual:@"dataTransferred"],@"Do not observe %@ for %@.",keyPath,object);
				[[tc transfer] setState:TransferStateTransferring description:@"Transferring..."];
				//todo: medium: ask for updated queue status for other transfers with this peer.
				[tc removeObserver:self forKeyPath:keyPath];
			}
		}
		else if([object isKindOfClass:[PeerConnectionsController class]] && [keyPath isEqual:@"peerConnectionUnableToConnect"]) {
			NSSet *s = [self transfersForPeer:[[object peerConnectionUnableToConnect] peer]];
			Transfer *t;
			NSEnumerator *e = [s objectEnumerator];
			while(t = [e nextObject]) {
				switch([t state]) {
					case TransferStateStarting:
					case TransferStateIdle:
					case TransferStateFailed: {
						NSString *reason = [[t peer] availability] == AvailabilityOffline?@"Logged off":@"Unable to connect";
						[self failTransfer:t reason:reason retryAfterDelay:240.0];
					}
				}
			}
		}
		else {	//dispatch peer connection message
			NSAssert2([object isKindOfClass:[PeerConnectionsController class]],@"Observed object:%@ should not be an instance of %@.",object,[object class]);
			NSAssert2([keyPath isEqual:@"peerConnectionWithNewMessage"],@"Observed invalid keyPath: %@ for object: %@.",keyPath,object);

			PeerConnection *pc = [object peerConnectionWithNewMessage];
			Message *m = [pc messageReceived];
			
			[self dispatchMessage:m fromPeerConnection:pc];
		}
	}
}

#pragma mark Accessor functions (retrieval)
- (NSSet *)transfersForPeer:(Peer *)p {
	NSMutableSet *s = [NSMutableSet set];
	NSEnumerator *e = [_transfersSet objectEnumerator];
	Transfer *t;
	while(t = [e nextObject]) {
		if([[t peer] isEqual:p])
			[s addObject:t];
	}
	return s;
}

- (NSSet *)transfersWithType:(TransferType)type {
	NSMutableSet *s = [NSMutableSet set];
	NSEnumerator *e = [_transfersSet objectEnumerator];
	Transfer *t;
	while(t = [e nextObject]) {
		if([t type] == type)
			[s addObject:t];
	}
	return s;
}

- (NSSet *)transfersForPeer:(Peer *)p withType:(TransferType)type {
	NSMutableSet *s = [NSMutableSet setWithSet:[self transfersForPeer:p]];
	[s intersectSet:[self transfersWithType:type]];
	return s;
}

#pragma mark KVO
- (NSArray *)transfersDownload { return [[self transfersWithType:TransferTypeDownload] allObjects]; }
- (NSArray *)transfersUpload { return [[self transfersWithType:TransferTypeUpload] allObjects]; }

- (Transfer *)transferCreated { return _transferCreated; }
- (Transfer *)transferFailed { return _transferFailed; }
- (Transfer *)transferFinished { return _transferFinished; }
- (Transfer *)transferRemoved { return _transferRemoved; }
- (Transfer *)transferStopped { return _transferStopped; }
- (Transfer *)transferStarted { return _transferStarted; }

- (NSArray *)transfersAll { return [_transfersSet allObjects]; }
- (NSArray *)transfersActive { 
	NSEnumerator *e = [_transfersSet objectEnumerator]; 
	Transfer *t;
	NSMutableArray *a = [NSMutableArray arrayWithCapacity:[_transfersSet count]];
	while(t = [e nextObject]) {
		if([t state] == TransferStateStarting || [t state] == TransferStateTransferring) 
			[a addObject:t];
	}
	return a;
}

- (NSArray *)transfersQueued { 
	NSEnumerator *e = [_transfersSet objectEnumerator]; 
	Transfer *t;
	NSMutableArray *a = [NSMutableArray arrayWithCapacity:[_transfersSet count]];
	while(t = [e nextObject]) {
		if([t state] == TransferStateIdle) 
			[a addObject:t];
	}
	return a;
}

- (TransfersQueue *)transfersQueue {
	return _transfersQueue;
}

#pragma mark actions - key value observing


- (void)connectForTransferWithPeerConnection:(PeerConnection *)peerConnection transfer:(Transfer *)transfer { 
	//we will connect.
	//"connected" will be the next observed value. 
	
	//fejta: TODO: low: this should all be moved to a connectionscontroller class.
	NSAssert(transfer!=nil,@"Transfer must be set.");
	NSAssert([[transfer peer] isEqual:[peerConnection peer]],@"Peers are not equal");
	
	[transfer setState:TransferStateStarting description:@"Connecting..."];
	TransferConnection *tc = [[TransferConnection alloc] initWithTransfer:transfer];
	[self observeTransferConnection:tc];
	[tc connectToHost:[peerConnection addressHost] onPort:[peerConnection portHost] withUsername:[[_engine serverConnectionController] username]];
	[tc release];
	//now the transfer opens a connections, sends a peer init of type F message.
	//it then sends the transfer token and depending on whether it is an upload or not
	//reads or writes the long long file offset.
}

- (void)endRequestPeerToConnectForTransferWithToken:(NSNumber *)tokenNumber {
	Transfer *t = [_tokensTransferIncomingRequested objectForKey:tokenNumber];
	
	if(t==nil) return;
	
	if([[_engine connectionsIdentifier] shouldRequestIncomingConnectionFirst]) {	//then connect directly second.
		[self connectForTransferWithPeerConnection:[_peerConnectionsController peerConnectionForPeer:[t peer]] transfer:t];
	}
	else { //we tried all we could!
		[t setState:TransferStateFailed description:@"Peer could not connect to us."];
	}
	
	[_tokensTransferIncomingRequested removeObjectForKey:tokenNumber];
}

- (void)endTransferConnection:(TransferConnection *)transferConnection {
	Transfer *t = [transferConnection transfer];
	
	if(t!=nil) {
//		NSLog(@"Ending transfer: %@.",[t share]);
		if([transferConnection sentHeader]) {
			switch([t type]) {
			
				case TransferTypeDownload: {
					if([t countBytesRemaining] <= 0) {
						int speed = [t speed];
						//inform the server of this guys upload speed.
						Message *m = [Message messageWithCode:UpdateUploadSpeed];
						[m putString:[[t peer] name]];
						[m putInt:speed];
						[[NSApp engine] sendServerMessage:m];
						NSString *source = [[t filenameBuffer] stringByStandardizingPath];
						NSString *dest = [[(TransferDownload *)t filenameFinished] stringByStandardizingPath];
						NSFileManager *fm = [NSFileManager defaultManager];
						unsigned tries=0;
						while([fm fileExistsAtPath:dest] && tries++ < 10) {
							NSString *ext = [dest pathExtension];
							NSString *num = [NSString stringWithFormat:@"%d",[_serverConnection tokenNext]];
							dest = [[[dest stringByDeletingPathExtension] stringByAppendingPathExtension:num] stringByAppendingPathExtension:ext];
						}
						BOOL moved=NO;
						if(tries <= 10) {
							[t setState:TransferStateTransferring description:@"Saving file..."];
							moved = [fm movePath:source toPath:dest handler:nil];
						}
						
						if(moved == NO) {
							//fejta: TODO: low: move all of these to constants to assist localization.
							NSString *description = @"Unable to save file.";
							if([fm fileExistsAtPath:source]==NO) description =@"Buffer file no longer exists.";
							else if([fm fileExistsAtPath:dest]==YES) description = @"Could not find a non-existent filename.";
							
							[t setState:TransferStateFailed description:description];
							[[t peer] addPrivileges:PeerPrivilegesUploaded];
						}
						else
							[t setState:TransferStateFinished description:@"Finished."];
						
					}
					else {	//bytes still remain.
							[self failTransfer:t reason:[_transfersManuallyStopped containsObject:t]?@"Stopped":@"Disconnected" retryAfterDelay:200.0];
					}
					break;
				}
				case TransferTypeUpload: {
					if([t countBytesRemaining] <= 0)
						[t setState:TransferStateFinished description:@"Finished."];
					else
						[t setState:TransferStateFailed description:[_transfersManuallyStopped containsObject:t]?@"Stopped":@"Disconnected"];
					[_transfersQueue dequeueTransfer:t];
					break;
				}
				default:
					[t setState:TransferStateNone description:@"Unknown Type"];
			}
		}
		else if([transferConnection piercingFirewall]==NO) { //transferConnection didn't send the header (thus connect)
			if([[_engine connectionsIdentifier] shouldRequestIncomingConnectionSecond]) {
				[self requestPeerToConnectForTransfer:t];
			}
			else
				[t setState:TransferStateFailed description:@"Could not connect"];
		}
		else if([transferConnection piercingFirewall]==YES) {
			Message *m = [Message messageWithCode:CantConnectToPeer];
			[m putInt:[transferConnection tokenFirewall]];
			[_serverConnection sendMessage:m];
		}
	}
	[self stopObservingTransferConnection:transferConnection];
}

- (void)failTransfer:(Transfer *)transfer reason:(NSString *)reason retryAfterDelay:(NSTimeInterval)delay {
	TransferState state = TransferStateFailed;
	if([reason isEqual:@"Queued"]) {
		[self performSelector:@selector(requestPlaceInQueueForTransfer:) withObject:transfer afterDelay:5.0];
		state = TransferStateIdle;
	}

	[transfer setState:state description:reason];
	if([reason isEqual:@"Queued"]==NO) {
		[self setValue:transfer forKey:@"transferFailed"];
		//fejta: TODO: what about restarting uploads?
		if([_transfersManuallyStopped containsObject:transfer]==NO && delay > 0 && [transfer type]==TransferTypeDownload)
			[self performSelector:@selector(downloadShare:) withObject:[transfer share] afterDelay:delay];
	}
	[_transfersQueue dequeueTransfer:transfer];
}
 

- (void)failTransferWithToken:(unsigned)token peer:(Peer *)peer reason:(NSString *)reason { 
	Transfer *t = [self transferWithToken:token];
	if(t && [[t peer] isEqual:peer])
		[self failTransfer:t reason:reason retryAfterDelay:300.0];
}


- (void)observeTransferConnection:(TransferConnection *)transferConnection {
	TransferConnection *old = [_transferConnectionsSet member:transferConnection];
	if(old) {
		if(old==transferConnection)
			return;
		NSLog(@"TRANSFER: stop observing the old one.");
		[self stopObservingTransferConnection:old];
	}
	[_transferConnectionsSet addObject:transferConnection];
	[transferConnection addObserver:self forKeyPath:@"connected" options:nil context:nil];
}



- (void)processMessageFromServer {
	//If the peer can't connect to us for a transfer,
	//they will send a connect to peer message (transfer type)
	//through the server.
	
	Message *m = [_serverConnection messageReceived];
	[m rewind];
	unsigned code = [m getCode];
	switch(code) {
		case ConnectToPeer: {
			NSString *name = [m getString];
			NSString *type = [m getString];
			if([type isEqual:TypeConnectionTransfer]) {
				int addr = [m getInt];
				int port = [m getInt];
				int pass = [m getInt]; //pierce firewall token.

				//fejta: TODO: low: maybe we should be getting the peer more efficiently...
				Peer *p = [[_peerConnectionsController peerConnectionForName:name] peer];

				TransferConnection *tc = [[TransferConnection alloc] initWithPeer:p];
				[self observeTransferConnection:tc];
				[tc addObserver:self forKeyPath:@"token" options:nil context:nil];
				[tc pierceFirewallWithToken:pass ForHost:MakeNSStringFromInternetAddress(addr) onPort:port];
				[tc release];
				//transfer connection will now connect to the peer and send a pierce firewall message with /pass/
				//for the token.  Then it will read the transfer token and update its /token/ key.
				//this will be observed by the transfers controller.
				//NSLog(@"Received request to connect to %@ for TRANSFER.  Connecting...",name);
			}
			break;
		}
		case CantConnectToPeer: {
			unsigned token = [m getInt];
			[self endRequestPeerToConnectForTransferWithToken:[NSNumber numberWithUnsignedInt:token]];
			break;
		}
	}
}
- (void)processFolderContentsResponse:(Message *)message fromPeerConnection:(PeerConnection *)peerConnection {
	[message decompress];
	NSNumber *n = [NSNumber numberWithUnsignedInt:[message getInt]];
	SharePeer *p = [_tokensFoldersDownloading objectForKey:n];
	if(p) {
		[self performSelector:@selector(requestTransfersForSharesInSameFolderAsShare:) withObject:p afterDelay:0.0];
	}
	else {
	//NSLog(@"TRANSFER: Unrequested folder contents with token %d from %@:%d.",[n unsignedIntValue],[peerConnection addressHost],[peerConnection portHost]);
	}
}

- (void)processQueueResponseWithCode:(int)code message:(Message *)message peerConnection:(PeerConnection *)peerConnection {
	NSString *filename = [message getString];
	TransferDownload *t = (TransferDownload *)[self transferWithFilename:filename peer:[peerConnection peer] type:TransferTypeDownload];
	
	if(code==PlaceInQueue && [t state]==TransferStateIdle) {
		unsigned place = [message getInt];
		[t setLocationQueue:place];
		[t setState:TransferStateIdle description:@"Queued"];
	}
	else if(code==QueueFailed) {
		[self failTransfer:t reason:[message getString] retryAfterDelay:300.0];
	}
}


- (void)processTransferRequest:(Message *)message fromPeerConnection:(PeerConnection *)peerConnection {
//NSLog(@"TRANSFER: %@ is asking is for a transfer.",[peerConnection peer]);
	TransferType direction = [message getInt];
	unsigned token = [message getInt];
	NSString *filename = [message getString];
	Message *response = [Message messageWithCode:TransferResponse];
	[response putInt:token];
	switch(direction) {
		case TransferTypeUpload: {
			unsigned long long size;
			if([message canReadBytes:sizeof(size)])
				size = [message getLongLong];
			else {
				NSLog(@"Upload Request from %@ only sent a 4-byte size",[peerConnection peer]);
				size = [message getInt];
			}
			
			Transfer *t = [self transferWithFilename:filename peer:[peerConnection peer] type:TransferTypeDownload];
			
			if(t==nil || [_transfersManuallyStopped containsObject:t]==YES || [t state]==TransferStateFinished ||
				[t state]==TransferStateTransferring) {
				//fejta: TODO: low: feature: accept pushed files from friends option?
				[response putByte:NO];
				[response putString:@"Not Requested"];
				//fejta: TODO: report this some how...
			}
			else {
				//now accept and wait for the connection.
				[response putByte:YES];
				[self waitForConnectionWithToken:token ForTransfer:t size:size];
			}
			break;
		}
		case TransferTypeDownload: {	//it is a download.
			//fejta: TODO: high: implement this for users who want to download a file from us.
			ShareLocal *share = [_shareController shareForSharedPath:filename];
			if(share) {
				TransferUpload *upload = [self transferWithFilename:filename peer:[peerConnection peer] type:TransferTypeUpload];
				if(upload == nil) {
					upload = [[TransferUpload alloc] initWithPeer:[peerConnection peer] token:[_serverConnection tokenNext] share:share];
				}
				else {
					[upload retain];
				}
				
				[upload setTokenPeerChosen:token];
				
				if([_transfersQueue queueTransfer:upload] == NO) {
					//it wasn't queued, we can immediately transfer it!
					[response putByte:YES];
					[response putLongLong:[share size]];
					[self waitForConnectionWithToken:token ForTransfer:upload size:0];
				}
				else {
					//it was queued.  
					[response putByte:NO];
					[response putString:@"Queued"];
					[upload setState:TransferStateIdle description:@"Queued"];
				}
				
				[self addTransfer:upload];
				[upload release];
			}
			else {
				[response putByte:NO];
				[response putString:@"Not Shared"];
			}
			break;
		}
		default: {
			[NSException raise:@"TransferTypeException" format:@"Unknown Transfer direction %d.",direction];
		}
	}
	[peerConnection sendMessage:response];
//NSLog(@"TRANSFER: Sent %@ a response for their request.",[peerConnection peer]);
}



- (void)processTransferResponse:(Message *)message fromPeerConnection:(PeerConnection *)peerConnection {
	//if it is allowed we will now open a transfer connection.
	//requestPeerToConnectForTransfer: will be called if we are not behind a firewall.
	//connectForTransferWithPeerConnection:transfer: will be called otherwise.
	unsigned token = [message getInt];
	char allowed = [message getByte];

	Transfer *t = [self transferWithToken:token];
	[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(timeoutTransfer:) object:t];

	
	if(t == nil) {
		NSLog(@"TRANSFER: Received an unrequrested response for transfer with token %d.",token);
		return;
	}
	
	if(allowed) {
		if([t type]==TransferTypeDownload && [message canReadBytes:sizeof(unsigned long long)])
			[(SharePeer *)[t share] setSize:[message getLongLong]];
		else if([t type]==TransferTypeDownload)
			NSLog(@"Unable to read size of transfer.");
		if([[_engine connectionsIdentifier] shouldRequestIncomingConnectionFirst]) 
			[self requestPeerToConnectForTransfer:t];
		else
			[self connectForTransferWithPeerConnection:peerConnection transfer:t];
	}
	else {	//not allowed...
		NSString *reason = [message canReadData]?[message getString]:@"Unknown Failure";
		[self failTransferWithToken:token peer:[peerConnection peer] reason:reason];
		[_transfersQueue dequeueTransfer:t];
	}
	
	[NSObject cancelPreviousPerformRequestsWithTarget:_transfersQueue selector:@selector(dequeueTransfer:) object:t];
}

- (void)queueUploadWithFilename:(NSString *)filename peerConnection:(PeerConnection *)peerConnection {
	ShareLocal *share = [_shareController shareForSharedPath:filename];
	if(share) {
		Peer *p = [peerConnection peer];
		NSAssert2(p != nil,@"PeerConnection must have an associated peer to queue file %@ for %@.",filename,peerConnection);
		TransferUpload *t = [self transferWithFilename:filename peer:p type:TransferTypeUpload];
		if(t==nil)
			t = [[TransferUpload alloc] initWithPeer:p token:[_serverConnection tokenNext] share:share];
		else
			[t retain];
		[self addTransfer:t];
		[_transfersQueue queueTransfer:t];
		[t release];
		[self sendPlaceInQueueForFilename:filename peerConnection:peerConnection];
	}
	else {
		Message *m = [Message messageWithCode:QueueFailed];
		[m putString:filename];
		[m putString:@"Not Shared"];
		[peerConnection sendMessage:m];
	}
}

- (void)requestPeerToConnectForTransfer:(Transfer *)transfer {
	NSAssert(transfer,@"Cannot find out which peer to request to connect to us because the transfer is nil.");
	[transfer setState:TransferStateStarting description:@"Asking peer to connect to us..."];

	Message *m = [Message messageWithCode:ConnectToPeer]; //followed by: token, username, type
	unsigned token = [_serverConnection tokenNext]; 
	[m putInt:token];
	[m putString:[[transfer peer] name]];
	[m putString:TypeConnectionTransfer];
	NSNumber *t = [NSNumber numberWithUnsignedInt:token];
	[_tokensTransferIncomingRequested setObject:transfer forKey:t];
	[transfer setState:TransferStateStarting description:@"Requesting Connection..."];
	[self performSelector:@selector(endRequestPeerToConnectForTransferWithToken:) withObject:t afterDelay:120.0];
	
	[_serverConnection sendMessage:m];
}

- (void)sendPlaceInQueueForFilename:(NSString *)filename peerConnection:(PeerConnection *)peerConnection {
//	NSString *filename = [message getString];
	
	Transfer *t = [self transferWithFilename:filename peer:[peerConnection peer] type:TransferTypeUpload];
	
	unsigned place = [_transfersQueue placeInQueue:t]+1;
	
	Message *placeInQueue = [Message messageWithCode:PlaceInQueue];
	[placeInQueue putString:filename];
	[placeInQueue putInt:place];
	[peerConnection sendMessage:placeInQueue];
}


- (void)sendRequestToUploadTransfer:(TransferUpload *)transfer {
//NSLog(@"Requesting upload...");
	NSAssert1([transfer peer]!=nil,@"Transfer:%@ must have a peer.",transfer);
	
	if([_transfersSet containsObject:transfer]==NO) 
		return;		//this will happen if we aren't busy enough to have a queue (we will start it in the reply to the request)!
	
	Message *m = [Message messageWithCode:TransferRequest];
	[m putInt:TransferTypeUpload];
	[m putInt:[transfer token]];
	[m putString:[[transfer share] filenameShared]];
	[m putLongLong:[[transfer share] size]];
	
	[[_peerConnectionsController peerConnectionForPeer:[transfer peer]] sendMessage:m];
	[_transfersQueue performSelector:@selector(dequeueTransfer:) withObject:transfer afterDelay:60.0];
}

- (void)stopObservingTransferConnection:(TransferConnection *)transferConnection {
	NSAssert(transferConnection != nil, @"transferConnection must be set to stop observing it.");

	[transferConnection removeObserver:self forKeyPath:@"token"];
	[transferConnection removeObserver:self forKeyPath:@"dataTransferred"];
	[transferConnection removeObserver:self forKeyPath:@"connected"];
	
	[_transferConnectionsSet removeObject:transferConnection];
}

- (void)timeoutTransfer:(Transfer *)transfer {
	if([transfer state] == TransferStateStarting)  {
		[self failTransfer:transfer reason:@"Timed out" retryAfterDelay:120.0];
	}
	[_transfersQueue dequeueTransfer:transfer];
}


- (void)timeoutWaitingForConnectionForTransfer:(Transfer *)transfer {
	[_transfersWaitingForConnection removeObject:transfer];
	switch([transfer type]) {
		case TransferTypeDownload: {
			if([transfer state]==TransferStateStarting) {
				[transfer setState:TransferStateIdle description:@"Requesting again..."];
				[(TransferDownload *)transfer setLocationQueue:0];
				[self performSelector:@selector(downloadShare:) withObject:[transfer share] afterDelay:60.0];
			}
			break;
		}
		case TransferTypeUpload: {
			[self failTransfer:transfer reason:@"Timed out" retryAfterDelay:0.0];
			break;
		}
	}
	[_transfersQueue dequeueTransfer:transfer];
}
	

- (Transfer *)transferWithToken:(unsigned)token {
	Transfer *t;
	NSEnumerator *e = [[self transfersAll] objectEnumerator];
	while(t = [e nextObject]) {
		if(token == [t token]) 
			return t;
	}
	return nil;
}

- (Transfer *)transferWithFilename:(NSString *)filename peer:(Peer *)peer type:(TransferType)type {
	//this method enumerates through the current set of Transfers returning whichever one matches the filename, peer and type.
	NSEnumerator *e = [[self transfersAll] objectEnumerator];
	Transfer *t;
			
	while(t = [e nextObject]) {
		if([t type]!=type)
			continue;
		if([peer isEqual:[t peer]] && [[[t share] filenameShared] isEqual:filename]) {
			//found the transfer!
			return t;
		}

		//else t isn't the right transfer, look at the next one.
	}
	
	return nil;
}

- (void)waitForConnectionWithToken:(unsigned)token ForTransfer:(Transfer *)t size:(unsigned)size {
	NSAssert(t!=nil,@"Must set transfer to wait for connection.");
	NSAssert([t state]!=TransferStateFinished&&[t state]!=TransferStateTransferring,@"Transfer must be starting, in an idle or unset state.");
	if(size)
		[(SharePeer *)[t share] setSize:size];
//NSLog(@"TRANSFER: Waiting for connection from %@ with token %d (old was %d).",[t peer],token,[t token]);
	[t setState:TransferStateStarting description:@"Waiting for connection..."];
	[t setTokenPeerChosen:token];
	[_transfersWaitingForConnection addObject:t];
	[self performSelector:@selector(timeoutWaitingForConnectionForTransfer:) withObject:t afterDelay:80.0];
}


#pragma mark actions - public

- (BOOL)saveDownload:(TransferDownload *)download toFile:(NSString *)file {
	return [NSKeyedArchiver archiveRootObject:download toFile:file];
}

- (BOOL)loadDownloadFromFile:(NSString *)file {
	id o = [NSKeyedUnarchiver unarchiveObjectWithFile:file];
	if(o==nil || [o isKindOfClass:[TransferDownload self]]==NO) {
		return NO;
	}
	else {
		NS_DURING
			[o openBuffer];	//finds out how many bytes were transferred.
			[o closeBuffer];
			[self requestTransfer:(TransferDownload *)o];
			NS_VALUERETURN(YES, BOOL);
		NS_HANDLER
			NS_VALUERETURN(NO,BOOL);
		NS_ENDHANDLER
	}
}



- (void)requestPlaceInQueueForTransfer:(Transfer *)t {
	if([t state]==TransferStateIdle) {
		Message *m = [Message messageWithCode:PlaceInQueueRequest];
		[m putString:[[t share] filenameShared]];
		[[[_engine peerConnectionsController] peerConnectionForPeer:[t peer]] sendMessage:m];
		
		float delay = [(TransferDownload *)t locationQueue]==0?60.0:300.0;
//		NSLog(@"TRANSFER: Asking %@ for our place in queue.  Will ask again in %f seconds.",[t peer],delay);
		[self performSelector:@selector(requestPlaceInQueueForTransfer:) withObject:t afterDelay:delay]; //every 5 min.
	}
}

- (TransferDownload *)transferForShare:(SharePeer *)file {
	//this method instantiates a TransferDownload based on the default save dir information,
	//the finished and temporary filenames.
	//it tries opening and closing the buffer to see the percentage complete the download is.
	unsigned token = [[_engine serverConnection] tokenNext];
	
	NSString *finishedDir = [[NSUserDefaults standardUserDefaults] stringForKey:SolarSeekDefaultPathDownloads];
	NSString *tempDir = [[NSUserDefaults standardUserDefaults] stringForKey:SolarSeekDefaultPathDownloadsIncomplete];

	NSString *finishedName = [[[file filenameShared] componentsSeparatedByString:@"\\"] lastObject];  //just the filename. no path	
	NSString *tempName = [NSString stringWithFormat:@"%@ - %@.solarseek",finishedName,[[file peer] name]];
	
	TransferDownload *t = [[[TransferDownload alloc] initWithPeer:[file peer] 
						token:token
						share:file 
						filenameTemporary:[tempDir stringByAppendingPathComponent:tempName]
						filenameFinished:[finishedDir stringByAppendingPathComponent:finishedName]
					] autorelease];
	[t openBuffer];	//finds out how many bytes were transferred.
	[t closeBuffer];
	[t setState:TransferStateIdle description:@"Queued"];
	return t;
}

- (void)downloadFolderOfShare:(SharePeer *)share {
	Message *m = [Message messageWithCode:FolderContentsRequest];
	unsigned token = [[_engine serverConnection] tokenNext];
	[m putInt:token];
	[m putData:[share dataDeletingLastPathComponent]];
	[[[_engine peerConnectionsController] peerConnectionForPeer:[share peer]] sendMessage:m];

	[_tokensFoldersDownloading setObject:share forKey:[NSNumber numberWithUnsignedInt:token]];
	[_tokensFoldersDownloading performSelector:@selector(removeObjectForKey:) withObject:[NSNumber numberWithUnsignedInt:token] afterDelay:120.0];
}

- (void)requestTransfer:(Transfer *)transfer {
	if([[[NSApp engine] serverConnectionController] loggedIn] == NO) {
		[transfer setState:TransferStateStarting description:@"Not Logged In"];
		[self addTransfer:transfer];
		[self performSelector:@selector(requestTransfer:) withObject:transfer afterDelay:10.0];
		return;
	}
	//this method will send a TransferRequest message to the peer associated with the transfer.
	//it will also add transfer to the current set.
//NSLog(@"TRANSFER: Requesting Transfer...");
	[_transfersManuallyStopped removeObject:transfer];
	Message *m = [Message messageWithCode:TransferRequest];
	[m putInt:[transfer type]];
	[m putInt:[transfer token]];
	if([transfer type]==TransferTypeDownload) {
		[m putData:[(SharePeer *)[transfer share] filenameData]];
	}
	else {
		[m putString:[[transfer share] filenameShared]];
	}
	[transfer setState:TransferStateStarting description:@"Requesting..."];
	[self performSelector:@selector(timeoutTransfer:) withObject:transfer afterDelay:20.0];
	[self addTransfer:transfer];
	[[[_engine peerConnectionsController] peerConnectionForPeer:[transfer peer]] sendMessage:m];
}

- (void)requestTransfersForSharesInSameFolderAsShare:(SharePeer *)share {
	@try {
		[[NSAlert alertWithMessageText:@"folder downloads not finished" defaultButton:@"OK" alternateButton:nil otherButton:nil informativeTextWithFormat:@"Not implemented."]
			beginSheetModalForWindow:[NSApp keyWindow] modalDelegate:nil didEndSelector:nil contextInfo:nil];
	}
	@catch(id ue) {
		NSLog([ue description]);
	}
//NSLog(@"TRANSFER: requesting folder information from %@...",[share peer]);
//	NSMutableSet *set = [NSMutableSet set];
//	NSEnumerator *e = [[[share peer] shares] objectEnumerator];
//	SharePeer *s;
//	while(s = [e nextObject]) {
//		if([[share dataDeletingLastPathComponent] isEqualToData:[s dataDeletingLastPathComponent]])
//			[set addObject:s];
//	}
//
//	[self downloadShares:set];
}

- (NSSet *)downloadShares:(id)members {
	//this function takes an enumeration of shares.
	//for each share it then retrieves the TransferDownload (instantiating if necessary)
	//and requests the transfer.
	
	//fejta: TODO: HIGH: need to work with in progress downloads.
	NSMutableSet *set = [NSMutableSet setWithCapacity:[members count]];
	NSEnumerator *e = [members objectEnumerator];
	SharePeer *s;
	Peer *old=nil;
	while(s = [e nextObject]) {
		Peer *p = [s peer];
		TransferDownload *td = [self transferWithFilename:[s filenameShared] peer:p type:TransferTypeDownload];
		if(td == nil)
			td = [self transferForShare:s];	//instantiates a new Transfer.
//		else 
//			NSLog(@"TRANSFER: Restarting transfer %@.",td);
		[set addObject:td];
		[self requestTransfer:td];
	}
		
	return set;
}

- (TransferDownload *)downloadShare:(SharePeer *)file {
	NS_DURING
		[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(downloadShare:) object:file];
		NS_VALUERETURN([[self downloadShares:[NSArray arrayWithObject:file]] anyObject],TransferDownload *);
	NS_HANDLER
		NSLog(@"what was the problem: %@",localException);
		NS_VALUERETURN(nil,TransferDownload *);
	NS_ENDHANDLER
}

- (void)cancelAllPerformSelectorsWithTransfer:(Transfer *)transfer {
	[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(requestTransfer:) object:transfer];
	[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(requestPlaceInQueueForTransfer:) object:transfer];
	[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(downloadShare:) object:[transfer share]];
	[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(requestTransfersForSharesInSameFolderAsShare:) object:[transfer share]];
//	[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(endRequestPeerToConnectForTransferWithToken:) object:transfer];
	[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(dequeueTransfer:) object:transfer];
	[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(timeoutWaitingForConnectionForTransfer:) object:transfer];
	[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(requestPlaceInQueueForTransfer:) object:transfer];
	[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(timeoutTransfer:) object:transfer];
}

- (void)stopTransfer:(Transfer *)transfer {
	NSEnumerator *e = [_transferConnectionsSet objectEnumerator];
	TransferConnection *tc;
	while(tc = [e nextObject]) {
		if([tc transfer] == transfer) {
			if([tc connected]) {
				[_transfersManuallyStopped addObject:transfer];
				[tc disconnect];
			}
			else {
				[self failTransfer:transfer reason:@"Stopped" retryAfterDelay:0.0];
			}
			return;
		}
	}
	if(tc==nil)
		[self failTransfer:transfer reason:@"Stopped" retryAfterDelay:0.0];
	[self cancelAllPerformSelectorsWithTransfer:transfer];
}

- (void)deleteTransfer:(Transfer *)transfer {
	[self stopTransfer:transfer];
	[transfer closeBuffer];
	if([transfer type]==TransferTypeDownload) {
		NSAssert([transfer isKindOfClass:[TransferDownload class]],@"Almost deleted a shared file.");
		[[NSFileManager defaultManager] removeFileAtPath:[[transfer filenameBuffer] stringByStandardizingPath] handler:nil];
	}
	[self removeTransfer:transfer];
}


#pragma mark actions - private

- (void)addTransfer:(Transfer *)t {
	//called by processTransferRequest
	//and requestTransfer
	unsigned c = [_transfersSet count];
	Transfer *old = [_transfersSet member:t];
	if(old && old==t)
		return;
	else if(old && old != t) {
//		NSLog(@"TEST: removed a transfer.");
		[self willChangeValueForKey:@"transferRemoved"];
		[t retain];
		[_transferRemoved release];
		_transferRemoved = t;
		[_transfersSet removeObject:t];
		[self didChangeValueForKey:@"transferRemoved"];
	}

	[_transfersSet addObject:[t retain]];
	[self willChangeValueForKey:@"transferCreated"];
	[_transferCreated release];
	_transferCreated = t;	//retained above.
	[self didChangeValueForKey:@"transferCreated"];
	if(old==nil && [[self transfersForPeer:[t peer]] count]==1) {	//fejta: TODO: this is probably buggy.
		[[NSApp engine] watchPeer:[t peer]];
		[[t peer] addObserver:self forKeyPath:@"availablity" options:NSKeyValueObservingOptionOld context:(void *)ObserveAvailable];
	}
}

- (void)removeTransfer:(Transfer *)t {
	unsigned c = [_transfersSet count];
	[_transfersSet removeObject:[t retain]];
	if(c != [_transfersSet count]) {
		[self willChangeValueForKey:@"transferRemoved"];
		[_transferRemoved release];
		_transferRemoved = t;
		[self didChangeValueForKey:@"transferRemoved"];
		if([[self transfersForPeer:[t peer]] count]==0) {
			[[NSApp engine] stopWatchingPeer:[t peer]]; //doesn't exist yet.
			[[t peer] removeObserver:self forKeyPath:@"availability"];
		}
	}
	else
		[t release];
}


@end
